package fr.cephb.operonj.core.db.database;

import java.io.IOException;
import java.io.Reader;

import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

import fr.cephb.operonj.core.bio.ChromInfo;
import fr.cephb.operonj.core.db.binding.SequenceBinding;
import fr.cephb.operonj.core.db.binding.SequenceIndexBinding;
import fr.cephb.operonj.core.db.comparator.SequenceIndexComparator;
import fr.cephb.operonj.core.db.key.SequenceIndex;
import fr.cephb.operonj.core.je.PrimaryDB;

public class GenomicSeqDB
	extends PrimaryDB<SequenceIndex,byte[]>
	{
	public static final String DEFAULT_NAME="index2sequence";
	private TupleBinding<SequenceIndex> KeyBinding= new SequenceIndexBinding();
	private TupleBinding<byte[]> valueBinding= new SequenceBinding();
	
	
	private class SequenceReader
		extends Reader
		{
		private Transaction txn=null;
		private SequenceIndex seqIndex;
		private int currIndex=0;
		private byte array[];
		
		@Override
		public int read(char[] cbuf, int off, int len) throws IOException
			{
			if(seqIndex==null) return -1;
			int nread=0;
			try
				{
				for(int i=0;i< len;++i)
					{

					if(currIndex<array.length)
						{
						assert(currIndex<SequenceIndex.SIZE);
						cbuf[off+i] =(char)array[currIndex++];
						nread++;
						}
					else if(currIndex==SequenceIndex.SIZE)
						{
						seqIndex=new SequenceIndex(
								seqIndex.getBuildId(),
								seqIndex.getChromId(),
								seqIndex.getIndex()+SequenceIndex.SIZE
								);
						
						this.array = GenomicSeqDB.this.get(txn, seqIndex);
						if(this.array==null)
							{
							this.seqIndex=null;
							break;
							}
						else
							{
							assert(this.array.length>0);
							this.currIndex=0;
							cbuf[off+i] =(char)array[currIndex++];
							nread++;
							}
						}
					else
						{
						assert(currIndex<SequenceIndex.SIZE);
						break;
						}
					}
				}
			catch (DatabaseException e)
				{
				throw new IOException(e);
				}
			
			return nread;
			}
		
		@Override
		public void close() throws IOException
			{
			
			}
		@Override
		public String toString() {
			return "SequenceReader:{ array-is-null :"+(array==null)+
				" array-length:"+(array==null?-1:array.length)+" currIndex:"+currIndex+
				" seqIndex:"+seqIndex+"}"
				;
			}
		}
	
	static private DatabaseConfig init(DatabaseConfig cfg)
		{
		cfg.setSortedDuplicates(false);
		cfg.setBtreeComparator(SequenceIndexComparator.class);
		return cfg;
		}
	
	public GenomicSeqDB(Environment env, Transaction txn,
			String databaseName, DatabaseConfig dbConfig)
			throws DatabaseException
		{
		super(env, txn, databaseName, init(defaultDbCfg(env,dbConfig)));
		}
	@Override
	public TupleBinding<SequenceIndex> getKeyBinding()
		{
		return KeyBinding;
		}
	@Override
	public TupleBinding<byte[]> getValueBinding()
		{
		return valueBinding;
		}
	
	public String getSequence(Transaction txn, int buildId,int chromId,
			int startIndex,int endIndex)
			throws DatabaseException
		{
		try
			{
			int length=endIndex-startIndex;
			if(length<0) throw new IllegalArgumentException(""+startIndex+" "+endIndex);
			char cbuf[]=new char[length];
			Reader w= getReader(txn, buildId, chromId, startIndex);
			int n=w.read(cbuf,0,length);
			w.close();
			if(n!=length) throw new RuntimeException(
				"Cannot get subsequence read:"+n+" length="+length+" build-id:"+buildId+
				" chromId:"+chromId+" start:"+startIndex+" end:"+endIndex+" w.reader"+w.toString()
				);
			return new String(cbuf,0,n);
			}
		catch (IOException e)
			{
			throw new RuntimeException(e);
			}
		}
	
	public void removeBuild(Transaction txn, int buildId) throws DatabaseException
		{
		clearInternalBuffers();
		Cursor c=null;
		try
			{
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data= new DatabaseEntry();
			c=openCursor(txn);
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				if(entryToKey(key).getBuildId()==buildId)
					{
					if(c.delete()!=OperationStatus.SUCCESS)
						{
						throw new RuntimeException("Cannot delete cursor");
						}
					}
				}
			}
		catch (DatabaseException e)
			{
			throw e;
			}
		finally
			{
			if(c!=null) c.close();
			}
		}
	

	
	public void removeChromosome(Transaction txn, ChromInfo chrom) throws DatabaseException
		{
		clearInternalBuffers();
		Cursor c=null;
		try
			{
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data= new DatabaseEntry();
			c=openCursor(txn);
			while(c.getNext(key, data, LockMode.DEFAULT)==OperationStatus.SUCCESS)
				{
				if( entryToKey(key).getBuildId()==chrom.getBuildId() &&
					entryToKey(key).getChromId()==chrom.getChromId()
					)
					{
					if(c.delete()!=OperationStatus.SUCCESS)
						{
						throw new RuntimeException("Cannot delete cursor");
						}
					}
				}
			}
		catch (DatabaseException e)
			{
			throw e;
			}
		finally
			{
			if(c!=null) c.close();
			}
		}
	
	@Override
	protected synchronized void clearInternalBuffers()
		{
		super.clearInternalBuffers();
		//this.bufferedSequenceIndex=null;
		//this.bufferedArray=null;
		}
	
	//private SequenceIndex bufferedSequenceIndex=null;
	//private byte bufferedArray[]=null;
	
	public synchronized Reader getReader(Transaction txn,int buildId,int chromId,int index) throws DatabaseException
		{
		int start=index-index%SequenceIndex.SIZE;
		
		SequenceReader reader= new SequenceReader();
		reader.txn= txn;
		
		
		reader.seqIndex= new SequenceIndex(buildId, chromId,start);
		reader.array= get(txn,reader.seqIndex);
		
		if(reader.array!=null)
			{
			reader.currIndex=(int)(index%SequenceIndex.SIZE);
			}
		else
			{
			LOG.info("Cannot retrieve "+reader.seqIndex);
			reader.seqIndex=null;
			}
		
		return reader;
		}
	
	@Override
	public String getDefaultName()
		{
		return DEFAULT_NAME;
		}
	
	}
